package cn.wizzer.app.crown.modules.services.impl.order;

import cn.wizzer.app.crown.modules.models.CrownBet;
import cn.wizzer.app.crown.modules.models.CrownMatch;
import cn.wizzer.app.crown.modules.models.CrownOrder;
import cn.wizzer.app.crown.modules.models.constant.CrownConstant;
import cn.wizzer.app.crown.modules.models.enums.SettleStatusEnum;
import cn.wizzer.app.crown.modules.services.CrownBetService;
import cn.wizzer.app.crown.modules.services.CrownMatchService;
import cn.wizzer.app.crown.modules.services.CrownSettleService;
import cn.wizzer.app.crown.modules.services.order.CrownOrderService;
import cn.wizzer.app.sys.modules.models.SysCurrency;
import cn.wizzer.app.sys.modules.models.constant.CurrencyConstant;
import cn.wizzer.app.sys.modules.services.SysCurrencyService;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.user.modules.models.User;
import cn.wizzer.app.user.modules.models.enums.BizTypeEnum;
import cn.wizzer.app.user.modules.services.UserService;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.app.user.modules.services.account.UserRebateService;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.base.service.CommonDBService;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

@IocBean
@Service(interfaceClass = CrownSettleService.class)
public class CrownSettleServiceImpl implements CrownSettleService {
    @Inject
    private CrownBetService betService;
    @Inject
    private CrownMatchService matchService;
    @Inject
    private CommonDBService commondbservice;
    @Inject
    private CrownOrderService orderService;
    @Reference(retries = 0)
    @Inject
    private UserAccountRecordService recordService;
    @Reference
    @Inject
    private SysParamService sysParamService;
    @Reference
    @Inject
    private UserRebateService rebateService;
    @Reference
    @Inject
    private UserService userService;
    @Inject
    @Reference
    private SysCurrencyService currencyService;

    private static final Log log = Logs.get();

    @Override
    public void settle(CrownOrder order) {
        // 关联投资
        CrownBet bet = betService.fetch(order.getBetId());
        // 关联赛事
        CrownMatch match = matchService.fetch(bet.getMatchId());
        // 结算订单
        this.settle(order, bet, match);
    }

    @Override
    public void refund(CrownOrder order) {
        order.setStatus(SettleStatusEnum.CANCELLED);
        this.handleRefundOrder(order);
    }

    /**
     * 结算订单
     *
     * @param order
     * @param bet
     * @param match
     */
    private void settle(CrownOrder order, CrownBet bet, CrownMatch match) {
        log.info("开始结算订单,userId:" + order.getUserId() + ",投资金额:" + order.getAmount() + ",orderNo:" + order.getNo());
        // 赛事异常，回退投资金额
        if (CrownConstant.OTHER.equals(match.getStage())) {
            order.setStatus(SettleStatusEnum.CANCELLED);
            this.handleRefundOrder(order);
        }
        // 赛事正常进行结算
        else if (CrownConstant.FINISHED.equals(match.getStage())) {
            // 计算输赢
            this.calOrderStatus(order, bet, match);
            // 赛事腰斩(退款)
            if (SettleStatusEnum.CANCELLED.equals(order.getStatus())) {
                this.handleRefundOrder(order);
            }
            // 非腰斩
            else {
                this.handleNormalOrder(order, bet);
            }
        } else {
            throw new BizException("赛事未结束，不能重新结算。赛事状态：" + match.getStage() + "。订单号：" + order.getNo());
        }

    }


    /**
     * 计算订单输赢状态
     *
     * @param bet
     * @param match
     */
    private void calOrderStatus(CrownOrder order, CrownBet bet, CrownMatch match) {
        switch (bet.getType()) {
            case HALF_PD:
                this.halfCourtPd(order, bet, match);
                break;
            case FULL_PD:
                this.fullCourtPd(order, bet, match);
                break;
            case FULL_GOALS:
                this.fullCourtInBall(order, bet, match);
                break;
            default:
        }
    }


    /**
     * 半场波胆玩法结算
     * 1-0 0-1 1-1 2-0 0-2 2-1 1-2 2-2...3-3 其他
     *
     * @param order
     * @param bet
     * @param match
     */
    private void halfCourtPd(CrownOrder order, CrownBet bet, CrownMatch match) {
        // 处理比分
        Map<String, Integer> scoresMap = this.handleScores(order, match.getHInBallHalf(), match.getCInBallHalf());
        // 如果不是赛事腰斩再进行计算
        if (null != scoresMap) {
            this.courtPd(order, bet, scoresMap.get("H"), scoresMap.get("C"));
        }
    }


    /**
     * 全场波胆玩法结算
     * 1-0 0-1 1-1 2-0 0-2 2-1 1-2 2-2...4-4 其他
     *
     * @param order
     * @param bet
     * @param match
     */
    private void fullCourtPd(CrownOrder order, CrownBet bet, CrownMatch match) {
        // 处理比分
        Map<String, Integer> scoresMap = this.handleScores(order, match.getHInBallFull(), match.getCInBallFull());
        // 如果不是赛事腰斩再进行计算
        if (null != scoresMap) {
            this.courtPd(order, bet, scoresMap.get("H"), scoresMap.get("C"));
        }
    }


    /**
     * @param order
     * @param bet
     * @param match
     */
    private void fullCourtInBall(CrownOrder order, CrownBet bet, CrownMatch match) {
        // 处理比分
        Map<String, Integer> scoresMap = this.handleScores(order, match.getHInBallFull(), match.getCInBallFull());
        // 如果不是赛事腰斩再进行计算
        if (null != scoresMap) {
            // 获取投资内容
            String betContent = bet.getBetContent();
            // 主客队全场总入球
            int allFullInBall = scoresMap.get("H") + scoresMap.get("C");
            // 当投资是7+
            if (betContent.contains("+")) {
                // 判断赛果总入球大于等于该投资系数文字则 全赢
                if (allFullInBall >= Integer.parseInt(betContent.replace("+", ""))) {
                    order.setResult(false);
                } else {
                    order.setResult(true);
                }
            } else {
                // 切割系数(0~1或2~3或4~6)
                double preCoeWord = Double.parseDouble(betContent.split("~")[0]);
                double nextCoeWord = Double.parseDouble(betContent.split("~")[1]);
                // 当投资的是其他值时，入球在系数的区间内则 全输
                if (preCoeWord <= allFullInBall && allFullInBall <= nextCoeWord) {
                    order.setResult(false);
                } else {
                    order.setResult(true);
                }
            }
        }
    }


    /**
     * 处理主客队比分，如果为腰斩设置order状态为CANCELLED
     *
     * @param order
     * @param hScore
     * @param cScore
     */
    private Map<String, Integer> handleScores(CrownOrder order, String hScore, String cScore) {
        if (CrownConstant.CUT.equals(hScore) || CrownConstant.CUT.equals(cScore)) {
            order.setStatus(SettleStatusEnum.CANCELLED);
            return null;
        } else {
            Map<String, Integer> scoreMap = new HashMap<>();
            scoreMap.put("H", Integer.parseInt(hScore));
            scoreMap.put("C", Integer.parseInt(cScore));
            return scoreMap;
        }
    }


    /**
     * 波胆玩法输赢计算
     *
     * @param order
     * @param bet
     * @param hScore 主队比分
     * @param cScore 客队比分
     */
    private void courtPd(CrownOrder order, CrownBet bet, int hScore, int cScore) {
        // 半场波胆，比分截止到3-3。全场波胆，比分截止到4-4。
        int value;
        switch (bet.getType()) {
            case HALF_PD:
                value = 3;
                break;
            case FULL_PD:
                value = 4;
                break;
            default:
                throw new BizException("波胆状态错误:" + bet.getType() + "。订单号：" + order.getNo());
        }
        // 投资‘其他’
        if ("other".equals(bet.getBetContent())) {
            // 如果赛果比分也是属于‘其他’范畴 则全赢
            if (hScore > value || cScore > value) {
                order.setResult(false);
            } else {
                // 赛果比分不属于‘其他’范畴 则全输
                order.setResult(true);
            }
        }
        // 投资的是具体比分
        else {
            // 切割出投资的主队上半场比分
            int mbBetBall = Integer.parseInt(bet.getBetContent().split("-")[0]);
            // 切割出投资的客队上半场比分
            int tgBetBall = Integer.parseInt(bet.getBetContent().split("-")[1]);
            // 如果投资比分和赛果比分相同则 全输
            if (mbBetBall == hScore && tgBetBall == cScore) {
                order.setResult(false);
            } else {
                // 如果投资比分和赛果比分不同则 全赢
                order.setResult(true);
            }
        }
    }


    /**
     * 处理退款订单
     *
     * @param order
     */
    @Aop(TransAop.SERIALIZABLE)
    private void handleRefundOrder(CrownOrder order) {
        order.setResult(false);
        // 设置结算时间
        order.setSettleAt(Times.getTS());
        // 更新order结算金额和结算状态
        orderService.update(order);
        // 返还投资金额
        String currencyId = this.getCurrencyId(order);
        log.info("投资金额返还,userId:" + order.getUserId() +
                ",投资金额:" + order.getAmount() +
                ",orderNo:" + order.getNo() +
                ",体验金投资:" + order.getExpInvest() +
                ",类型:" + BizTypeEnum.INVEST_REVERT);
        recordService.add(currencyId, order.getUserId(), order.getAmount(), BizTypeEnum.INVEST_REVERT, order.getId(), order.getNo());
        log.info("结算订单结束,userId:" + order.getUserId() + ",投资金额:" + order.getAmount() + ",orderNo:" + order.getNo());
    }


    /**
     * 处理正常订单
     *
     * @param order
     * @param bet
     */
    @Aop(TransAop.SERIALIZABLE)
    private void handleNormalOrder(CrownOrder order, CrownBet bet) {
        // 获取币种id
        String currencyId = this.getCurrencyId(order);
        // 全输
        if (!order.getResult()) {
            // 如果是保本投资，退回本金 超过100$则最多返还100$
            if (bet.getProtect()) {
                // 保本投资最多返还100$
                if (order.getAmount().compareTo(BigDecimal.valueOf(100.000000)) > 0) {
                    order.setResultAmount(BigDecimal.valueOf(100.000000));
                } else {
                    order.setResultAmount(order.getAmount());
                }
                log.info("保本赛事退本金,userId:" + order.getUserId() +
                        ",本金:" + order.getResultAmount() +
                        ",orderNo:" + order.getNo() +
                        ",是否体验金投资:" + order.getExpInvest() +
                        ",类型:" + BizTypeEnum.INVEST_INCOME);
                recordService.add(currencyId, order.getUserId(), order.getResultAmount(), BizTypeEnum.INVEST_INCOME, order.getId(), order.getNo());
            }
            // 非保本赛事则‘结算金额’为0
            else {
                order.setResultAmount(BigDecimal.ZERO);
            }
        }
        // 全赢
        else {
            // 中奖则计算金额
            this.calSimplexProfit(order);
            log.info("结算金额划转,userId:" + order.getUserId() +
                    ",结算金额:" + order.getResultAmount() +
                    ",orderNo:" + order.getNo() +
                    ",是否体验金投资:" + order.getExpInvest() +
                    ",类型:" + BizTypeEnum.INVEST_INCOME);
            // 金额划转
            recordService.add(currencyId, order.getUserId(), order.getResultAmount(), BizTypeEnum.INVEST_INCOME, order.getId(), order.getNo());
        }
        // 更新状态
        order.setStatus(SettleStatusEnum.SETTLED);
        // 设置结算时间
        order.setSettleAt(Times.getTS());
        // 更新order结算金额和结算状态
        orderService.update(order);
        // 更新用户有效流水(非体验金 并且非保本 投资才算有效流水)
        if (!order.getExpInvest()) {
            if (!bet.getProtect()) {
                User user = userService.fetch(order.getUserId());
                user.setEffectQuota(user.getEffectQuota().add(order.getAmount()));
                userService.update(user);
            }
        }

        // 代理返佣
        rebateService.settleRebate(order);
        log.info("结算订单结束,userId:" + order.getUserId() +
                ",投资金额:" + order.getAmount() +
                ",结算金额:" + order.getResultAmount() +
                ",是否体验金投资:" + order.getExpInvest() +
                ",orderNo:" + order.getNo());
    }

    /**
     * 结算金额计算
     * 结算金额 ：本金 * 赔率 - (本金 * 赔率 * 服务费百分比) + 本金
     *
     * @param order
     * @return
     */
    private void calSimplexProfit(CrownOrder order) {
        // 手续费占比
        BigDecimal feeRatio = sysParamService.fetch().getServiceFee().divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
        // 利润
        BigDecimal profit = order.getAmount().multiply(order.getRate().divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
        // 服务费
        BigDecimal serviceFee = profit.multiply(feeRatio).setScale(2, RoundingMode.HALF_UP);
        // 结算金额
        BigDecimal resultAmount = profit.subtract(serviceFee).add(order.getAmount());
        order.setResultAmount(resultAmount);
        order.setServiceFee(serviceFee);
    }


    /**
     * 默认投资币种USD。如果是体验金返回体验金币种id。
     * 后续也可以扩展成多币种投资时查询币种id
     *
     * @param order
     * @return
     */
    private String getCurrencyId(CrownOrder order) {
        String currencyId = CurrencyConstant.DEFAULT;
        if (order.getExpInvest()) {
            SysCurrency currency = currencyService.fetch(Cnd.where("name", "=", CurrencyConstant.EXP));
            currencyId = currency.getId();
        }
        return currencyId;
    }

}
